#include <functional>
#include <iostream>
#include <map>
#include <memory>
#include <utility>

// Copy Global
int g = 1;

auto bar = [g = g]() { return g + 1; };

// Generic Lambda
class Sum {
public:
  template <typename... Args> constexpr auto operator()(Args &&...args) {
    return (std::forward<Args>(args) + ...);
  }
};
// Callable Objects
class Fib {

public:
  long operator()(const long n) {
    return (n <= 2) ? 1 : operator()(n - 1) + operator()(n - 2);
  }
};

int main(int argc, char *argv[]) {
  // Callable Objects
  {
    Fib fib;
    std::cout << "Callable Object fib(20) = " << fib(20) << "\n";
  }
  // lambda
  {
    std::function<long(long)> fib = [&](long n) {
      return (n <= 2) ? 1 : fib(n - 1) + fib(n - 2);
    };

    std::cout << "lambda fib(20) = " << fib(20) << "\n";
  }

  // Default Arguments

  {
    auto fib = [](long n = 0) {
      long a = 0, b = 1;
      for (long i = 0; i < n; ++i) {
        long tmp = b;
        b = a + b;
        a = tmp;
      }
      return a;
    };

    std::cout << fib() << "\n";
    std::cout << fib(50) << "\n";
  }

  // Captureless function pointer
  {

    long (*fib)(long) = [](long n) {
      long a = 0, b = 1;
      for (long i = 0; i < n; ++i) {
        long tmp = b;
        b = a + b;
        a = tmp;
      }
      return a;
    };
    std::cout << fib(100) << "\n";
  }

  // Lambda capture initializers
  {
    std::unique_ptr<int> p = std::make_unique<int>(5566);
    auto f = [x = std::move(p)]() { std::cout << *x << "\n"; };

    f();
  }

  // Capture by std::move define move Constructor [f=std::move(foo)] () {}

  // Copy a Global into a Capture
  {
    int g = 10;
    std::cout << bar() << "\n";
  }

  // constexpr by Default
  {
    auto fib = [](long n = 0) {
      long a = 0, b = 1;
      for (long i = 0; i < n; ++i) {
        long tmp = b;
        b = a + b;
        a = tmp;
      }
      return a;
    };

    static_assert(fib(10) == 55, "constexpr by Default disable");
  }

  // Generic Lambda
  {

    Sum sum;
    constexpr int ret = sum(1, 2, 3, 4, 5);
    std::cout << ret << "\n";
  }

  // redo by lambda
  {
    auto sum = [](auto &&...args) {
      return (std::forward<decltype(args)>(args) + ...);
    };
    constexpr int ret = sum(1, 2, 3, 4, 5);
    std::cout << ret << "\n";
  }

  // compare function

  {
    auto cmp = [](auto &lhs, auto &rhs) { return lhs < rhs; };

    // sort by keys
    std::map<int, std::string, decltype(cmp)> m(cmp);

    m[1] = "Baz";
    m[2] = "Bar";
    m[3] = "Foo";

    for (auto it : m) {
      std::cout << it.first << ", " << it.second << "\n";
    }
  }

  return 0;
}
